package springboard.auth.webapp.controller;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import common.exception.ConflictException;
import common.exception.ForbiddenException;
import common.exception.NotFoundException;
import common.lang.Transactionary;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import springboard.auth.bean.*;
import springboard.auth.service.AuthService;
import springfox.documentation.annotations.ApiIgnore;

import java.security.Principal;
import java.util.Date;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    AuthService authService;

    @Autowired
    Transactionary tx;

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class WholeUser extends User {
        String username;
        String password;
    }

    @ApiOperation("分页查询用户")
    @GetMapping("/users")
    @RequiresPermissions("users:r")
    public Page<User> listUsers(@ApiParam("关键字（匹配Id、昵称、手机号及邮箱）") @RequestParam(value = "keyword", required = false) String keyword,
                                @ApiParam("注册时间（起）") @RequestParam(value = "createdTime0", required = false)  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date createdTime0,
                                @ApiParam("注册时间（止，不包含）") @RequestParam(value = "createdTime1", required = false)  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date createdTime1,
                                @ApiParam("页码（0开始）") @RequestParam(value = "page", defaultValue = "0") int page,
                                @ApiParam("记录数/页") @RequestParam(value = "pageSize", required = false) Integer pageSize) {
        int[] pagination = pageSize != null ? new int[] {page, pageSize} : new int[] {page};
        return authService.listUsers(keyword, createdTime0, createdTime1, pagination);
    }

    @ApiOperation("搜索用户")
    @GetMapping("/users/search")
    @RequiresPermissions("users:r")
    public List<User> findUsers(@ApiParam("关键字（匹配Id、昵称、手机号及邮箱）") @RequestParam(value = "keyword", required = false) String keyword) {
        return authService.listUsers(keyword, null, null).getContent();
    }

    @Data
    public static class UserBundle {
        User user;
        List<Account> accounts;
        List<Role> roles;
        List<Permission> permissions;
    }

    @ApiOperation("获取用户详情（包含账号、角色、权限）")
    @GetMapping("/users/{id}")
    @RequiresPermissions("users:r")
    public UserBundle detailUser(@ApiParam("用户Id") @PathVariable("id") long id) {
        User user = authService.getUser(id);
        if(user == null) throw new NotFoundException();
        UserBundle result = new UserBundle();
        result.setUser(user);
        result.setAccounts(authService.findUserAccounts(id));
        result.setPermissions(authService.findUserPermissions(id));
        result.setRoles(authService.findUserRoles(id));
        return result;
    }

    @ApiOperation("获取当登入用户详情（包含账号、角色、权限）")
    @GetMapping("/me")
    @RequiresAuthentication
    public UserBundle detailMe(@ApiIgnore User subjectUser) {
        UserBundle result = new UserBundle();
        result.setUser(subjectUser);
        result.setAccounts(authService.findUserAccounts(subjectUser.getId()));
        result.setPermissions(authService.findUserPermissions(subjectUser.getId()));
        result.setRoles(authService.findUserRoles(subjectUser.getId()));
        return result;
    }

    @ApiOperation("新建用户及账号")
    @PostMapping("/users")
    @RequiresPermissions("users:rw")
    public User createUser(@RequestBody WholeUser user) {
        try {
            return tx.transact(txStatus -> {
                authService.createUser(user);
                if(StringUtils.hasText(user.username) && StringUtils.hasText(user.password)) {
                    Account account = new Account();
                    account.setUsername(user.username);
                    account.setPlainPassword(user.password);
                    account.setUserId(user.getId());
                    authService.createAccount(account);
                }
                return user;
            });
        } catch(DuplicateKeyException x) {
            throw new ConflictException("用户名已经被占用。");
        }
    }

    @ApiOperation("更新用户及账号")
    @PostMapping("/users/{id}")
    @RequiresPermissions("users:rw")
    public User updateUser(@ApiParam("用户Id") @PathVariable("id") long id,
                           @RequestBody WholeUser user) {
        user.setId(id);
        return tx.transact(txStatus -> {
            authService.updateUser(user);
            Account account = authService.getAccount(user.username);
            if(account == null && StringUtils.hasText(user.username) && StringUtils.hasText(user.password)) {
                account = new Account();
                account.setUsername(user.username);
                account.setPlainPassword(user.password);
                account.setUserId(user.getId());
                authService.createAccount(account);
            } else if(account != null && StringUtils.hasText(user.password)) {
                authService.updateUserPassword(user.getId(), user.password);
            }
            return user;
        });
    }

    @ApiOperation("更新用户状态")
    @PostMapping("/users/{id}/status")
    @RequiresPermissions("users:rw")
    public Short setUserStatus(@ApiParam("用户Id") @PathVariable("id") long id,
                               @ApiParam("状态：0=启用，-1=停用") @RequestParam("status") Short status) {
        User user = authService.getUser(id);
        user.setStatus(status);
        authService.updateUser(user);
        return status;
    }

    @ApiOperation("更新我的信息")
    @PostMapping("/me")
    @RequiresAuthentication
    public User updateMe(@RequestBody WholeUser user,
                         @ApiIgnore User subjectUser) {
        if(!user.getId().equals(subjectUser.getId()))
            throw new ForbiddenException();
        if(StringUtils.hasText(user.getPhone()) && !user.getPhone().equals(subjectUser.getPhone()))
            user.setPhoneStatus(User.PHONE_STATUS_UNVERIFIED);
        if(StringUtils.hasText(user.getMail()) && !user.getMail().equals(subjectUser.getMail()))
            user.setMailStatus(User.MAIL_STATUS_UNVERIFIED);
        authService.updateUser(user);
        updatePrincipal(user);
        return user;
    }

    public static void updatePrincipal(User user) {
        Session session = SecurityUtils.getSubject().getSession();
        PrincipalCollection pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
        UserPrincipal userPrincipal = (UserPrincipal)pc.getPrimaryPrincipal();
        userPrincipal.setUser(user);
        session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, pc);
    }

    @ApiOperation("更新我的密码")
    @PostMapping("/my/password")
    @RequiresAuthentication
    public String updateMyPassword(@ApiParam("新密码") @RequestParam("password") String password,
                                   @ApiParam("旧密码") @RequestParam("oldPassword") String oldPassword,
                                   @ApiIgnore User subjectUser) {
        User confirmedUser = authService.authc(((Principal)SecurityUtils.getSubject().getPrincipal()).getName(), oldPassword);
        if(confirmedUser == null || !confirmedUser.getId().equals(subjectUser.getId())) throw new ForbiddenException();
        String encodedPassword = authService.updateUserPassword(subjectUser.getId(), password);
        SecurityUtils.getSubject().logout();
        return encodedPassword;
    }

    @ApiOperation("获取全部权限列表")
    @GetMapping("/permissions")
    @RequiresPermissions("users:r")
    public List<Permission> listPermissions() {
        return authService.listPermissions();
    }

    @ApiOperation("获取全部角色列表")
    @GetMapping("/roles")
    @RequiresPermissions("users:r")
    public List<Role> listRoles() {
        return authService.listRoles();
    }

    @Data
    public static class RoleBundle {
        Role role;
        List<Permission> permissions;
    }

    @ApiOperation("获取角色详情（角色、包含权限）")
    @GetMapping("/roles/{key}")
    @RequiresPermissions("users:r")
    public RoleBundle detailRole(@ApiParam("角色键") @PathVariable("key") String key) {
        Role role = authService.getRole(key);
        if(role == null) throw new NotFoundException();
        RoleBundle result = new RoleBundle();
        result.setRole(role);
        result.setPermissions(authService.findRolePermission(key));
        return result;
    }

    @ApiOperation("创建角色")
    @PostMapping("/roles")
    @RequiresPermissions("users:rw")
    public Role createRole(@RequestBody Role role) {
        try {
            role.setPredefined(false);
            return authService.createRole(role);
        } catch(DuplicateKeyException x) {
            throw new DuplicateKeyException("该角色值已存在");
        }
    }

    @ApiOperation("更新角色")
    @PostMapping("/roles/{key}")
    @RequiresPermissions("users:rw")
    public Role updateRole(@ApiParam("角色键") @PathVariable("key") String key,
                           @RequestBody Role role) {
        role.setPredefined(false);
        return authService.updateRole(role, key);
    }

    @ApiOperation("删除角色")
    @PostMapping("/roles/{key}/remove")
    @RequiresPermissions("users:rw")
    public Integer removeRole(@ApiParam("角色键") @PathVariable("key") String key) {
        return authService.deleteRole(key);
    }

    @ApiOperation("为用户赋予角色列表和权限列表")
    @PostMapping("/user/{id}/grant")
    @RequiresPermissions("users:rw")
    public Integer grantUser(@ApiParam("用户Id") @PathVariable("id") long id,
                             @ApiParam("权限键列表") @RequestParam(value = "permissionKey") String[] permissionKeys,
                             @ApiParam("角色键列表") @RequestParam(value = "roleKey") String[] roleKeys) {
        int r = authService.ungiveUserRoles(id);
        r += authService.ungiveUserPermissions(id);
        if(!ArrayUtils.isEmpty(permissionKeys))
            r += authService.giveUserPermissions(id, permissionKeys);
        if(!ArrayUtils.isEmpty(roleKeys))
            r += authService.giveUserRoles(id, roleKeys);
        return r;
    }

    @ApiOperation("为角色赋予权限列表")
    @PostMapping("/role/{key}/grant")
    @RequiresPermissions("users:rw")
    public Integer grantRole(@ApiParam("角色键") @PathVariable("key") String key,
                             @ApiParam("权限键列表") @RequestParam(value = "permissionKey") String[] permissionKeys) {
        int r = authService.ungiveRolePermissions(key);
        if(!ArrayUtils.isEmpty(permissionKeys))
            r += authService.giveRolePermissions(key, permissionKeys);
        return r;
    }

    @ApiOperation("分页查询账号")
    @GetMapping("/accounts")
    @RequiresPermissions("users:r")
    public Page<Account> listUserAccounts(@ApiParam("关键字（匹配用户名）") @RequestParam(value = "keyword", required = false) String keyword,
                                          @ApiParam("页码（0开始）") @RequestParam(value = "page", defaultValue = "0") int page,
                                          @ApiParam("记录数/页") @RequestParam(value = "pageSize", required = false) Integer pageSize) {
        int[] pagination = pageSize != null ? new int[] {page, pageSize} : new int[] {page};
        return authService.listUserAccounts(keyword, pagination);
    }

    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public @ResponseBody
    String handleException(DuplicateKeyException x) {
        log.warn(x.getMessage());
        return x.getMessage();
    }

}
